﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PdfReportWriter.cs" company="OxyPlot">
//   The MIT License (MIT)
//
//   Copyright (c) 2012 Oystein Bjorke
//
//   Permission is hereby granted, free of charge, to any person obtaining a
//   copy of this software and associated documentation files (the
//   "Software"), to deal in the Software without restriction, including
//   without limitation the rights to use, copy, modify, merge, publish,
//   distribute, sublicense, and/or sell copies of the Software, and to
//   permit persons to whom the Software is furnished to do so, subject to
//   the following conditions:
//
//   The above copyright notice and this permission notice shall be included
//   in all copies or substantial portions of the Software.
//
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
//   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
//   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
//   IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
//   CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
//   TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
//   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
// <summary>
//   PDF report writer using MigraDoc.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace OxyPlot.Pdf
{
    using System;
    using System.IO;

    using MigraDoc.DocumentObjectModel;
    using MigraDoc.Rendering;

    using OxyPlot.Reporting;

    using Paragraph = MigraDoc.DocumentObjectModel.Paragraph;

    /// <summary>
    /// Provides a report writer for portable document format using MigraDoc.
    /// </summary>
    public class PdfReportWriter : IDisposable, IReportWriter
    {
        /// <summary>
        /// The current section.
        /// </summary>
        private Section currentSection;

        /// <summary>
        /// The disposed flag.
        /// </summary>
        private bool disposed;

        /// <summary>
        /// Flag if the document has been closed.
        /// </summary>
        private bool isClosed;

        /// <summary>
        /// The style.
        /// </summary>
        private ReportStyle style;

        /// <summary>
        /// Initializes a new instance of the <see cref="PdfReportWriter"/> class.
        /// </summary>
        /// <param name="filename">
        /// The FileName.
        /// </param>
        public PdfReportWriter(string filename)
        {
            this.FileName = filename;
            this.Document = new Document();
        }

        /// <summary>
        /// Gets or sets the pdf document.
        /// </summary>
        protected Document Document { get; set; }

        /// <summary>
        /// Gets or sets the file name.
        /// </summary>
        protected string FileName { get; set; }

        /// <summary>
        /// Gets the current section.
        /// </summary>
        private Section CurrentSection
        {
            get
            {
                return this.currentSection ?? (this.currentSection = this.Document.AddSection());
            }
        }

        /// <summary>
        /// Defines the styles used in the document.
        /// </summary>
        /// <param name="document">
        /// The document.
        /// </param>
        /// <param name="reportStyle">
        /// The report Style.
        /// </param>
        public static void DefineStyles(Document document, ReportStyle reportStyle)
        {
            SetStyle(document.Styles["Normal"], reportStyle.BodyTextStyle);

            // Heading1 to Heading9 are predefined styles with an outline level. An outline level
            // other than OutlineLevel.BodyText automatically creates the outline (or bookmarks)
            // in PDF.
            for (int i = 0; i < reportStyle.HeaderStyles.Length; i++)
            {
                SetStyle(document.Styles["Heading" + (i + 1)], reportStyle.HeaderStyles[i]);
            }

            // style = document.Styles[StyleNames.Header];
            // style.ParagraphFormat.AddTabStop("16cm", TabAlignment.Right);

            // style = document.Styles[StyleNames.Footer];
            // style.ParagraphFormat.AddTabStop("8cm", TabAlignment.Center);
            Style style = document.Styles.AddStyle("TableHeader", "Normal");
            SetStyle(style, reportStyle.TableHeaderStyle);

            style = document.Styles.AddStyle("TableCaption", "Normal");
            SetStyle(style, reportStyle.TableCaptionStyle);

            style = document.Styles.AddStyle("TableText", "Normal");
            SetStyle(style, reportStyle.TableTextStyle);

            style = document.Styles.AddStyle("FigureText", "Normal");
            SetStyle(style, reportStyle.FigureTextStyle);
        }

        /// <summary>
        /// Closes this instance.
        /// </summary>
        public virtual void Close()
        {
            if (this.isClosed)
            {
                return;
            }

            var r = new PdfDocumentRenderer { Document = this.Document };
            r.RenderDocument();
            r.PdfDocument.Save(this.FileName);
            this.isClosed = true;
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// The write drawing.
        /// </summary>
        /// <param name="d">
        /// The d.
        /// </param>
        public void WriteDrawing(DrawingFigure d)
        {
            Paragraph p = this.WriteStartFigure(d);
            this.CurrentSection.AddParagraph("Drawings are not implemented.");
            this.WriteEndFigure(d, p);
        }

        /// <summary>
        /// The write end figure.
        /// </summary>
        /// <param name="f">
        /// The f.
        /// </param>
        /// <param name="pa">
        /// The pa.
        /// </param>
        public void WriteEndFigure(Figure f, Paragraph pa)
        {
            pa.AddLineBreak();
            pa.AddFormattedText(f.GetFullCaption(this.style), "FigureText");
        }

        /// <summary>
        /// The write equation.
        /// </summary>
        /// <param name="equation">
        /// The equation.
        /// </param>
        public void WriteEquation(Equation equation)
        {
            Paragraph p = this.CurrentSection.AddParagraph();
            p.AddText("Equations are not supported.");
        }

        /// <summary>
        /// The write header.
        /// </summary>
        /// <param name="h">
        /// The h.
        /// </param>
        public void WriteHeader(Header h)
        {
            if (h.Text == null)
            {
                return;
            }

            if (h.Level == 1)
            {
                this.currentSection = null;
            }

            this.CurrentSection.AddParagraph(h.Text, "Heading" + h.Level);
        }

        /// <summary>
        /// The write image.
        /// </summary>
        /// <param name="i">
        /// The i.
        /// </param>
        public void WriteImage(Image i)
        {
            Paragraph p = this.WriteStartFigure(i);
            if (i.Source != null)
            {
                MigraDoc.DocumentObjectModel.Shapes.Image pi = p.AddImage(Path.GetFullPath(i.Source));
                pi.Width = Unit.FromCentimeter(15);
            }

            this.WriteEndFigure(i, p);
        }

        /// <summary>
        /// The write paragraph.
        /// </summary>
        /// <param name="p">
        /// The p.
        /// </param>
        public void WriteParagraph(Reporting.Paragraph p)
        {
            this.CurrentSection.AddParagraph(p.Text ?? string.Empty);
        }

        /// <summary>
        /// The write plot.
        /// </summary>
        /// <param name="plot">
        /// The plot.
        /// </param>
        public void WritePlot(PlotFigure plot)
        {
            if (this.FileName == null)
            {
                return;
            }

            var directory = Path.GetDirectoryName(this.FileName);
            if (directory == null)
            {
                return;
            }

            var source = string.Format(
                "{0}_Plot{1}.pdf", Path.GetFileNameWithoutExtension(this.FileName), plot.FigureNumber);
            var sourceFullPath = Path.Combine(directory, source);

            PdfExporter.Export(plot.PlotModel, sourceFullPath, plot.Width, plot.Height);

            var p = this.WriteStartFigure(plot);
            MigraDoc.DocumentObjectModel.Shapes.Image pi = p.AddImage(sourceFullPath);
            pi.Width = Unit.FromCentimeter(15);

            this.WriteEndFigure(plot, p);
        }

        /// <summary>
        /// Writes the report.
        /// </summary>
        /// <param name="report">
        /// The report.
        /// </param>
        /// <param name="reportStyle">
        /// The report style.
        /// </param>
        public void WriteReport(Report report, ReportStyle reportStyle)
        {
            this.style = reportStyle;
            DefineStyles(this.Document, reportStyle);
            report.Write(this);
        }

        /// <summary>
        /// Writes the start of a figure.
        /// </summary>
        /// <param name="f">
        /// The figure.
        /// </param>
        /// <returns>
        /// A paragraph
        /// </returns>
        public Paragraph WriteStartFigure(Figure f)
        {
            return this.CurrentSection.AddParagraph();
        }

        /// <summary>
        /// Writes the table.
        /// </summary>
        /// <param name="table">
        /// The table.
        /// </param>
        public void WriteTable(Table table)
        {
            if (table.Rows == null)
            {
                return;
            }

            var pdfTable = new MigraDoc.DocumentObjectModel.Tables.Table
                {
                    Borders = { Width = 0.25, Left = { Width = 0.5 }, Right = { Width = 0.5 } },
                    Rows = { LeftIndent = 0 }
                };

            //// pdfTable.Style = "Table";
            //// pdfTable.Borders.Color = TableBorder;

            int columns = table.Columns.Count;

            for (int j = 0; j < columns; j++)
            {
                var pdfColumn = pdfTable.AddColumn();

                // todo: the widths are not working
                //// pdfColumn.Width = Unit.FromMillimeter(table.Columns[j].ActualWidth);
                pdfColumn.Format.Alignment = ConvertToParagraphAlignment(table.Columns[j].Alignment);
            }

            foreach (var tr in table.Rows)
            {
                var pdfRow = pdfTable.AddRow();
                for (int j = 0; j < columns; j++)
                {
                    bool isHeader = tr.IsHeader || table.Columns[j].IsHeader;

                    var c = tr.Cells[j];
                    var pdfCell = pdfRow.Cells[j];
                    pdfCell.AddParagraph(c.Content ?? string.Empty);
                    pdfCell.Style = isHeader ? "TableHeader" : "TableText";
                    pdfCell.Format.Alignment = ConvertToParagraphAlignment(table.Columns[j].Alignment);
                }
            }

            // table.SetEdge(0, 0, t.Columns.Count, t.Items.Count(), Edge.Box, BorderStyle.Single, 1.5, Colors.Black);
            var pa = this.CurrentSection.AddParagraph();
            pa.AddFormattedText(table.GetFullCaption(this.style), "TableCaption");

            this.CurrentSection.Add(pdfTable);
        }

        /// <summary>
        /// Converts paragraph alignment.
        /// </summary>
        /// <param name="alignment">
        /// The alignment.
        /// </param>
        /// <returns>
        /// The pdf alignment.
        /// </returns>
        private static ParagraphAlignment ConvertToParagraphAlignment(Alignment alignment)
        {
            switch (alignment)
            {
                case Alignment.Left:
                    return ParagraphAlignment.Left;
                case Alignment.Center:
                    return ParagraphAlignment.Center;
                case Alignment.Right:
                    return ParagraphAlignment.Right;
                default:
                    return ParagraphAlignment.Justify;
            }
        }

        /// <summary>
        /// The set style.
        /// </summary>
        /// <param name="style">
        /// The style.
        /// </param>
        /// <param name="ps">
        /// The ps.
        /// </param>
        private static void SetStyle(Style style, ParagraphStyle ps)
        {
            style.Font.Name = ps.FontFamily;
            style.Font.Size = Unit.FromPoint(ps.FontSize);
            style.Font.Bold = ps.Bold;
            style.Font.Italic = ps.Italic;
            style.Font.Color = ToMigraDocColor(ps.TextColor);
            style.ParagraphFormat.PageBreakBefore = ps.PageBreakBefore;
            style.ParagraphFormat.LeftIndent = Unit.FromPoint(ps.LeftIndentation);
            style.ParagraphFormat.RightIndent = Unit.FromPoint(ps.RightIndentation);
            style.ParagraphFormat.LineSpacing = Unit.FromPoint(ps.LineSpacing);
            style.ParagraphFormat.SpaceBefore = Unit.FromPoint(ps.SpacingBefore);
            style.ParagraphFormat.SpaceAfter = Unit.FromPoint(ps.SpacingAfter);
        }

        /// <summary>
        /// Converts an OxyColor to a migra doc color.
        /// </summary>
        /// <param name="c">
        /// The color.
        /// </param>
        /// <returns>
        /// The converted color.
        /// </returns>
        private static Color ToMigraDocColor(OxyColor c)
        {
            return new Color(c.A, c.R, c.G, c.B);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing">
        /// <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.
        /// </param>
        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    this.Close();
                }
            }

            this.disposed = true;
        }

    }
}